/**
 * 公共记录器工具（Logger Recorders）
 *
 * @fileoverview 提供“按运行目录”归档的通用记录方法，供预约/自动化等模块复用。
 *
 * 能力概览：
 * - 运行目录初始化：确保每次运行单独的目录，并写入 run-meta.json；可选择性设置环境变量
 * - 统一快照：基于配置（logging.save_html / logging.save_snapshots）落盘 HTML + PNG + 页面信息 JSON
 * - 网络日志：将页面 request/response 以 JSONL 方式追加写入 network-log.jsonl
 * - 辅助保存：直接按名称写入 HTML 到当前运行目录
 *
 * 目录规则：
 * - 开发：写入项目根目录的 booking-run-snapshots / automation-run-snapshots
 * - 生产：写入 userData 下同名目录
 * - 目录名：YYYY-MM-DD_HH-mm-ss_<id|passportTail>_<name>
 *
 * 使用建议：
 * 1) 在一次运行开始时调用 ensureRunDirectory(kind, label, true)，并调用 attachPageNetworkLogging(page, kind)
 * 2) 在各关键步骤调用 saveUnifiedSnapshot(page, stepName, kind)
 * 3) 如需保存中间 HTML 片段，调用 saveHtmlToRunDir(name, html, kind)
 */
import type { Page } from 'playwright'
import { appendFile, mkdir, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import process from 'node:process'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import { getAppDataPath, getConfig } from '../config/index'
import { createContextLogger, generateTraceId, log, PerformanceLogger } from './index'

// 统一启用 dayjs utc 插件，便于使用 utcOffset(8) 生成中国时区时间
try { dayjs.extend(utc) }
catch {}

/**
 * 运行类型：
 * - booking：预约模块（落盘至 booking-run-snapshots）
 * - automation：自动化模块（落盘至 automation-run-snapshots）
 */
type RunKind = 'booking' | 'automation' | 'request'

// 惰性获取上下文日志器，避免在应用尚未 setupLogger() 时抛错
function getCtx() {
  try {
    return createContextLogger({ component: 'Recorders' })
  }
  catch {
    return {
      info: (_m: string, _meta?: any) => {},
      debug: (_m: string, _meta?: any) => {},
      warn: (_m: string, _meta?: any) => {},
      error: (_m: string, _meta?: any) => {},
    } as any
  }
}

/**
 * 初始化“本次运行”的快照目录，并写入 run-meta.json。
 *
 * 行为：
 * - 根据环境（dev/prod）选择基础目录，创建子目录（时间戳 + 标识 + 姓名）
 * - 可选：设置 BOOKING_SNAPSHOT_DIR / AUTOMATION_SNAPSHOT_DIR 环境变量，供下游使用
 *
 * @param kind 运行类型（booking | automation）
 * @param label 目录标识对象
 * @param label.name 用于构造目录名的可读姓名（会做安全化处理）
 * @param label.id 可选，数值/字符串ID；若提供优先用于目录名的中间段
 * @param label.passportTail 可选，护照后四位；当未提供 id 时用于中间段
 * @param setEnv 是否写入当前进程环境变量（默认 true）
 * @returns 生成的目录绝对路径
 *
 * @example
 * await ensureRunDirectory('booking', { name: '张三', passportTail: 'A123' })
 */
export async function ensureRunDirectory(kind: RunKind, label: { name?: string, id?: string, passportTail?: string } = {}, setEnv = true): Promise<string> {
  const isDev = process.env.NODE_ENV !== 'production'
  const base = isDev
    ? join(process.cwd(), kind === 'booking' ? 'booking-run-snapshots' : (kind === 'automation' ? 'automation-run-snapshots' : 'request-run-snapshots'))
    : join(getAppDataPath(), kind === 'booking' ? 'booking-run-snapshots' : (kind === 'automation' ? 'automation-run-snapshots' : 'request-run-snapshots'))

  const ts = formatCNDateTime()
  const namePart = sanitizeForFilename(label.name || 'unknown')
  const idPart = label.id ? String(label.id) : (label.passportTail || 'xxxx')
  const dirName = `${ts}_${idPart}_${namePart}`

  const dir = join(base, dirName)
  try { await mkdir(dir, { recursive: true }) }
  catch {}

  if (setEnv) {
    try {
      if (kind === 'booking')
        process.env.BOOKING_SNAPSHOT_DIR = dir
      else if (kind === 'automation')
        process.env.AUTOMATION_SNAPSHOT_DIR = dir
      else
        process.env.REQUEST_SNAPSHOT_DIR = dir
    }
    catch {}
  }

  const meta = {
    runAt: new Date().toISOString(),
    kind,
    label,
  }
  try { await writeFile(join(dir, 'run-meta.json'), JSON.stringify(meta, null, 2), 'utf8') }
  catch {}
  getCtx().info('已初始化运行目录', { kind, dir })
  return dir
}

/**
 * 获取当前运行的快照目录。
 *
 * 优先级：环境变量（BOOKING_SNAPSHOT_DIR/AUTOMATION_SNAPSHOT_DIR）> 默认目录（dev: 项目根；prod: userData）
 */
export async function getRunSnapshotsPath(kind: RunKind): Promise<string> {
  const envDir = kind === 'booking' ? process.env.BOOKING_SNAPSHOT_DIR : (kind === 'automation' ? process.env.AUTOMATION_SNAPSHOT_DIR : process.env.REQUEST_SNAPSHOT_DIR)
  if (envDir && typeof envDir === 'string' && envDir.length > 0)
    return envDir
  const isDev = process.env.NODE_ENV !== 'production'
  if (isDev)
    return join(process.cwd(), kind === 'booking' ? 'booking-run-snapshots' : (kind === 'automation' ? 'automation-run-snapshots' : 'request-run-snapshots'))
  return join(getAppDataPath(), kind === 'booking' ? 'booking-run-snapshots' : (kind === 'automation' ? 'automation-run-snapshots' : 'request-run-snapshots'))
}

/**
 * 保存统一快照（HTML + PNG + info.json）。
 *
 * 受配置控制：
 * - logging.save_html：是否保存 HTML
 * - logging.save_snapshots：是否保存截图
 *
 * @param page Playwright Page 实例
 * @param name 快照名称（文件前缀，不含扩展名）
 * @param kind 运行类型（默认 booking）
 * @returns 写入的文件路径（可选字段，取决于开关）
 */
export async function saveUnifiedSnapshot(page: Page, name: string, kind: RunKind = 'booking'): Promise<{ htmlPath?: string, screenshotPath?: string, infoPath?: string }> {
  const opId = generateTraceId()
  PerformanceLogger.start(`recorders-snapshot-${opId}`)
  const cfg = getConfig() as any
  const saveHtml = !!(cfg?.logging?.save_html)
  const savePng = !!(cfg?.logging?.save_snapshots)
  try {
    const dir = await getRunSnapshotsPath(kind)
    try { await mkdir(dir, { recursive: true }) }
    catch {}
    const timestamp = formatCNDateTime()
    const base = join(dir, `${timestamp}_${name}`)

    let htmlPath: string | undefined
    let shotPath: string | undefined

    if (saveHtml) {
      try {
        const html = await page.content()
        htmlPath = `${base}.html`
        await writeFile(htmlPath, html, 'utf8')
      }
      catch {}
    }
    if (savePng) {
      try {
        shotPath = `${base}.png`
        await page.screenshot({ path: shotPath, fullPage: true })
      }
      catch {}
    }
    const info: any = { url: '', title: '', at: new Date().toISOString(), viewport: null as any, userAgent: '' }
    try { info.url = page.url() }
    catch {}
    try { info.title = await page.title() }
    catch {}
    try { info.viewport = page.viewportSize?.() || null }
    catch {}
    try { info.userAgent = await page.evaluate(() => navigator.userAgent) }
    catch {}
    const infoPath = `${base}.json`
    try { await writeFile(infoPath, JSON.stringify(info, null, 2), 'utf8') }
    catch {}
    const dur = PerformanceLogger.end(`recorders-snapshot-${opId}`)
    getCtx().debug('统一快照保存完成', { name, kind, duration: dur, durationText: `${dur}ms`, htmlPath, screenshotPath: shotPath, infoPath })
    return { htmlPath, screenshotPath: shotPath, infoPath }
  }
  catch (error) {
    const dur = PerformanceLogger.measure(`recorders-snapshot-${opId}`) || 0
    try {
      if (error instanceof Error)
        log.system.error(error, 'Recorders.saveUnifiedSnapshot', { duration: dur, name, kind })
      else
        getCtx().error('统一快照保存失败', { name, kind, error: String(error), duration: dur })
    }
    catch {
      // 如果日志系统尚未初始化，则忽略
    }
    return {}
  }
}

/**
 * 附加网络请求/响应日志到当前页面，输出 JSONL 文件到“按运行目录”。
 *
 * 说明：
 * - 文件：network-log.jsonl
 * - 内容：request(method/url/resourceType/headers)、response(url/status/headers)
 * - 幂等：内部有 enabled 保护，防重复注册
 */
export function attachPageNetworkLogging(page: Page, kind: RunKind = 'booking'): void {
  let enabled = false
  const getPath = async (): Promise<string> => {
    const dir = await getRunSnapshotsPath(kind)
    try { await mkdir(dir, { recursive: true }) }
    catch {}
    return join(dir, 'network-log.jsonl')
  }
  const write = async (obj: any) => {
    try { await appendFile(await getPath(), `${JSON.stringify(obj)}\n`, 'utf8') }
    catch {}
  }
  if (enabled)
    return
  enabled = true
  page.on('request', (req) => {
    void write({ t: Date.now(), type: 'request', method: req.method(), url: req.url(), resourceType: req.resourceType(), headers: req.headers() })
  })
  page.on('response', (res) => {
    void (async () => {
      try {
        const headers = await res.headers()
        await write({ t: Date.now(), type: 'response', url: res.url(), status: res.status(), headers })
      }
      catch {}
    })()
  })
}

/**
 * 追加步骤流水到 steps.jsonl（便于后续复盘步骤与耗时）。
 */
export async function appendStepRecord(kind: RunKind, record: any): Promise<void> {
  try {
    const dir = await getRunSnapshotsPath(kind)
    try { await mkdir(dir, { recursive: true }) }
    catch {}
    const line = `${JSON.stringify({ t: Date.now(), ...record })}\n`
    await appendFile(join(dir, 'steps.jsonl'), line, 'utf8')
  }
  catch {}
}

/**
 * 将给定 HTML 文本保存到当前运行目录，文件名为“时间戳_名称.html”。
 */
export async function saveHtmlToRunDir(name: string, html: string, kind: RunKind = 'booking'): Promise<string> {
  try {
    const dir = await getRunSnapshotsPath(kind)
    try { await mkdir(dir, { recursive: true }) }
    catch {}
    const timestamp = formatCNDateTime()
    const path = join(dir, `${timestamp}_${name}.html`)
    await writeFile(path, html, 'utf8')
    return path
  }
  catch { return '' }
}

/**
 * 生成安全的文件/目录名（保留中文；过滤非法字符与控制字符；限制长度）
 */
function sanitizeForFilename(input: string, fallback = 'unknown'): string {
  try {
    const raw = String(input || '').trim()
    const s = raw.replace(/[/:*?"<>|]/g, '_')
    let filtered = ''
    for (let i = 0; i < s.length; i++) {
      const code = s.charCodeAt(i)
      if ((code >= 0 && code <= 31) || code === 127)
        continue
      filtered += s[i]
    }
    const safe = filtered.replace(/\s+/g, ' ').trim()
    return safe.length > 60 ? `${safe.slice(0, 60)}…` : (safe || fallback)
  }
  catch { return fallback }
}

/**
 * 以中国时区格式化当前时间（YYYY-MM-DD_HH-mm-ss）
 */
function formatCNDateTime(): string {
  try {
    return dayjs().utcOffset(8).format('YYYY-MM-DD_HH-mm-ss')
  }
  catch {
    // 极端情况下 dayjs 失效时的兜底
    const now = new Date()
    const utcMs = now.getTime() + now.getTimezoneOffset() * 60000
    const chinaMs = utcMs + 8 * 60 * 60000
    const d = new Date(chinaMs)
    const y = d.getFullYear()
    const m = String(d.getMonth() + 1).padStart(2, '0')
    const day = String(d.getDate()).padStart(2, '0')
    const hh = String(d.getHours()).padStart(2, '0')
    const mm = String(d.getMinutes()).padStart(2, '0')
    const ss = String(d.getSeconds()).padStart(2, '0')
    return `${y}-${m}-${day}_${hh}-${mm}-${ss}`
  }
}
